﻿
using System;
using System.Threading;
using agsXMPP;
using agsXMPP.protocol.client;
using agsXMPP.protocol.x.muc;
using agsXMPP.protocol.x.muc.iq.admin;
using sharpxbot.Core.Configuration;
using sharpxbot.Core.Connection;
using sharpxbot.Core.Threading;
namespace sharpxbot.Core.Muc
{
    /// <summary>
    /// Represents active watcher for given group chat. Joins group-chat and watches for joined status.
    /// Rejoins room if needed ( e.g. temporal network failure etc)
    /// </summary>
    public class GroupChat : IActive, IGroupChat
    {
        #region Properties

        /// <summary>
        /// Gets connection for this watcher
        /// </summary>
        public IConnection Connection { get; private set; }

        /// <summary>
        /// Gets group-chat configuration for this watcher
        /// </summary>
        public IGroupChatConfiguration Configuration { get; private set; }

        /// <summary>
        /// Gets current presence information for group-chat
        /// </summary>
        public RoomPresenceInfo Presence { get; private set; }

        #endregion

        /// <summary>
        /// Creates new instance of watcher using given connection and group-chat configuration
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="configuration"></param>
        public GroupChat(IConnection connection, IGroupChatConfiguration configuration)
        {
            if (connection == null || configuration == null)
                throw new NullReferenceException("Argument null exception. MucManager constructor");

            this.Connection = connection;
            this.Configuration = configuration;

            IsRunning = false;
            waitEvent = new ManualResetEvent(true);

            Presence = new RoomPresenceInfo()
            {
                PollDate = DateTime.Now,
                PresenceType = PresenceType.unavailable
            };
            RoomJid = new Jid(Configuration.RoomJid);
            RoomJid.Resource = Configuration.ResourceName;
        }

        #region IActive Members

        /// <summary>
        /// Gets value indicating whether this watcher is running
        /// </summary>
        public bool IsRunning { get; private set; }

        /// <summary>
        /// Starts watcher, joins group-chat and preforms periodical monitoring of presence in group-chat
        /// </summary>
        public void Start()
        {
            if (!IsRunning)
            {
                thread = new Thread(new ThreadStart(Run));
                thread.Start();
                IsRunning = true;
            }
        }

        /// <summary>
        /// Stops watcher and leaves room
        /// </summary>
        public void Stop()
        {
            if (IsRunning)
            {
                terminate = true;
            }
        }

        #endregion

        #region IGroupChat Members

        /// <summary>
        /// Gets jid of this group chat
        /// </summary>
        public Jid RoomJid { get; private set; }

        public void KickUser(Jid room, string nick, string reason)
        {
            KickOccupant(room, nick, reason);
        }

        /// <summary>
        /// Leaves room for a given timeout the rejoins it
        /// </summary>
        /// <param name="timeout">Positive timeout in milliseconds</param>
        public void LeaveRoom(int timeout)
        {
            if (timeout <= 0)
                return;

            waitTimeout = timeout;
            waitEvent.Reset();

            LeaveRoom();
            Presence.PresenceType = PresenceType.unavailable;
        }

        #endregion

        #region Private methods

        private void Run()
        {
            while (!terminate)
            {
                waitEvent.WaitOne(waitTimeout);

                if (Presence.PresenceType == PresenceType.unavailable 
                    || Presence.PresenceType == PresenceType.error)
                    JoinRoom();

                Thread.Sleep(15000);
            }

            PostStop();
        }

        private void PostStop()
        {
            LeaveRoom();
            IsRunning = false;
        }

        private void LeaveRoom()
        {
            UnSubscribeEvents();
            LeaveRoomImpl(RoomJid, Configuration.ResourceName);
        }

        private void JoinRoom()
        {
            waitEvent.Set();

            if (Connection.State == XmppConnectionState.SessionStarted)
            {
                SubscribeEvents();
                JoinRoomImpl(RoomJid, Configuration.ResourceName, true);
            }
        }

        private void connection_OnPresence(object sender, Presence pres)
        {
            if (IsSelfPresence(pres))
            {
                Presence.PollDate = DateTime.Now;
                Presence.PresenceType = pres.Type;

                if (pres.Type == PresenceType.unavailable) // kicked or left room
                    UnSubscribeEvents();

                if (pres.Type == PresenceType.error)
                    UnSubscribeEvents();
            }
        }

        private void connection_OnSessionStarted(object parameter)
        {
            /*
             * This occurs when connection with server has been down and reestablished.
             * 
             * Since presence stills "avaliable" we shold manually rejoin group-chat
             * 
             */

            UnSubscribeEvents();
            JoinRoom();
        }

        private void connection_OnClose(object parameter)
        {
            Presence.PresenceType = PresenceType.unavailable;
            LeaveRoom();
        }

        private void SubscribeEvents()
        {
            Connection.OnSessionStarted += connection_OnSessionStarted;
            Connection.OnPresence += connection_OnPresence;
            Connection.OnClose += connection_OnClose;
        }

        private void UnSubscribeEvents()
        {
            Connection.OnSessionStarted -= connection_OnSessionStarted;
            Connection.OnPresence -= connection_OnPresence;
            Connection.OnClose -= connection_OnClose;
        }


        private bool IsSelfPresence(Presence pres)
        {
            return pres.From.Bare == RoomJid.Bare &&
                pres.From.Resource == RoomJid.Resource;
        }

        #endregion

        #region Low level methods

        private void LeaveRoomImpl(Jid roomJid, string resource)
        {
            Jid to = roomJid;
            to.Resource = resource;

            Presence pres = new Presence();
            pres.To = to;
            pres.Type = PresenceType.unavailable;

            Connection.Send(pres);
        }

        private void JoinRoomImpl(Jid roomJid, string resource, bool disableHistory)
        {
            Jid to = roomJid;
            to.Resource = resource;

            Presence pres = new Presence();
            pres.To = to;
            agsXMPP.protocol.x.muc.Muc x = new agsXMPP.protocol.x.muc.Muc();


            //disable history for groupchat
            if (disableHistory)
            {
                History hist = new History();
                hist.MaxCharacters = 0;
                x.History = hist;
                pres.AddChild(x);
            }

            Connection.Send(pres);
        }

        public void KickOccupant(Jid room, string nickname, string reason)
        {
            ChangeRoleImpl(Role.none, room, nickname, reason);
        }

        private void ChangeRoleImpl(Role role, Jid room, string nickname, string reason)
        {
            AdminIq aIq = new AdminIq();
            aIq.To = room;
            aIq.Type = IqType.set;

            agsXMPP.protocol.x.muc.iq.admin.Item itm = new agsXMPP.protocol.x.muc.iq.admin.Item();
            itm.Role = role;
            itm.Nickname = nickname;

            if (reason != null)
                itm.Reason = reason;

            aIq.Query.AddItem(itm);

            Connection.Send(aIq);
        }

        #endregion

        #region Private fields

        private Thread thread;
        private bool terminate;

        private ManualResetEvent waitEvent;
        private int waitTimeout;

        #endregion
    }

    public class RoomPresenceInfo
    {
        public DateTime PollDate { get; set; }
        public PresenceType PresenceType { get; set; }
    }
}
